/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#include "mesh.h"
#include "renderqueue.h"
#include "archivemanager.h"
#include "tinyxmlreader.h"
#include "graphic.h"

#include <sstream>
#include <limits>

#ifdef max
#undef max
#endif

#ifdef min
#undef min
#endif

#ifdef _DEBUG
#ifdef _MSC_VER
   #define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
#endif
#endif // _DEBUG

namespace Engine
{
	using namespace std;
	/**********************************************************************************************************/
	Mesh* MeshFactory::CreateInstance( const std::string& name )
	{
		return new Mesh(name);
	}
	/**********************************************************************************************************/
	Mesh::Mesh(const std::string& name)
		:Resource(name)
		,m_vertexCount(0)
		,m_currentVertexIndex(0)
		,m_vertexElementOffset(0)
		,m_vertexDataPtr(0)
	{
	}
	/**********************************************************************************************************/
	Mesh::~Mesh()
	{
	}
	/**********************************************************************************************************/
	const AABB& Mesh::GetBoundingBox(void) const
	{
		return m_boundingBox;
	}
	/**********************************************************************************************************/
	unsigned int Mesh::GetNumSubMeshes(void) const
	{
		return m_subMeshes.size();
	}
	/**********************************************************************************************************/
	SubMeshPtr Mesh::GetSubMesh(unsigned int index)
	{
		if(index < m_subMeshes.size() && index >= 0)
			return *(m_subMeshes.begin() + index);
		return SubMeshPtr();
	}
	/**********************************************************************************************************/
	void Mesh::ReadMesh(RefPtr<XmlElement> pElem,const std::string& name)
	{
		if(name == "mesh")
		{
			TraverseElemets(pElem->FirstChildElement(),mem_fun2_obj(this,&Mesh::ReadObjects));
		}
		else
			throw std::logic_error("Unknown root node < " + name + " >");
	}
	/**********************************************************************************************************/
	void Mesh::ReadObjects(RefPtr<XmlElement> pElem,const std::string& name)
	{
		if(name == "submesh")
		{
			if(pElem->FirstChildElement().IsNull())
				throw std::logic_error("Missing child nodes for SUBMESH node");
			if(pElem->FirstAttribute().IsNull())
				throw std::logic_error("Missing attributes for SUBMESH node");

			RefPtr<SubMesh,WeakPtrExtension> subMesh(new SubMesh());
			subMesh->Read(pElem);
			m_subMeshes.push_back(subMesh);
		}
		else if(name == "vertices")
		{
			if(m_vertexCount != 0)
				throw std::logic_error("Mesh " + GetName() + " already has vertex buffer.");
			if(pElem->FirstChildElement().IsNull())
				throw std::logic_error("Missing child nodes for VERTICES node");
			if(pElem->FirstAttribute().IsNull())
				throw std::logic_error("Missing attributes for VERTICES node");

			m_vertexElementOffset = 0;
			m_vertexDeclaration = Graphic::GetSingleton().GetRenderSystem()->CreateVertexDeclaration();

			TraverseAttributes(pElem->FirstAttribute(),mem_fun2_obj(this,&Mesh::ReadVertexDeclaration));

			m_vertexData = Graphic::GetSingleton().GetRenderSystem()->CreateVertexBuffer(m_vertexCount, m_vertexDeclaration->GetVertexSize(0));

			m_currentVertexIndex = 0;
			m_vertexDataPtr = m_vertexData->Lock();
			m_boundingBox.Reset();
			m_boundingBox.SetExtents(Vector3<REAL>(numeric_limits<REAL>::max(),numeric_limits<REAL>::max(),numeric_limits<REAL>::max()),Vector3<REAL>(numeric_limits<REAL>::min(),numeric_limits<REAL>::min(),numeric_limits<REAL>::min()));

			TraverseElemets(pElem->FirstChildElement(),mem_fun2_obj(this,&Mesh::ReadVertexData));

			m_vertexData->UnLock();

		}
		else
			throw std::logic_error("Unknown child node <" + name + " > for SCENE");
	}
	/**********************************************************************************************************/
	void Mesh::ReadVertexDeclaration(const std::string& name, const std::string& value)
	{
		if(name == "count")
		{
			std::stringstream buf(value);
			buf >> 	m_vertexCount;
		}
		else if(name == "position")
		{
			bool hasPosition = false;
			if(value == "true")
				hasPosition = true;
			else
			{
				std::stringstream buf(value);
				buf >> hasPosition;
			}
			if(hasPosition)
			{
				const VertexElement& elem = m_vertexDeclaration->AddElement(0, m_vertexElementOffset, VET_FLOAT3, VES_POSITION);
				m_vertexElementOffset += elem.GetSize();
			}
		}
		else if(name == "normal")
		{
			bool hasNormal = false;
			if(value == "true")
				hasNormal = true;
			else
			{
				std::stringstream buf(value);
				buf >> hasNormal;
			}
			if(hasNormal)
			{
				const VertexElement& elem = m_vertexDeclaration->AddElement(0, m_vertexElementOffset, VET_FLOAT3, VES_NORMAL);
				m_vertexElementOffset += elem.GetSize();
			}
		}
		else if(name == "color")
		{
			bool hasColor = false;
			if(value == "true")
				hasColor = true;
			else
			{
				std::stringstream buf(value);
				buf >> hasColor;
			}
			if(hasColor)
			{
				const VertexElement& elem = m_vertexDeclaration->AddElement(0, m_vertexElementOffset, VET_COLOR, VES_DIFFUSE);
				m_vertexElementOffset += elem.GetSize();
			}
		}
		else if(name == "texture_coords")
		{
			unsigned short textureCoordsCount = 0;
			std::stringstream buf(value);
			buf >> textureCoordsCount;
			for(unsigned short i = 0; i < textureCoordsCount; ++i)
			{
				const VertexElement& elem = m_vertexDeclaration->AddElement(0, m_vertexElementOffset, VET_FLOAT2, VES_TEXTURE_COORDINATES, i);
				m_vertexElementOffset += elem.GetSize();
			}
		}
		else
			throw std::logic_error("Unknown attribute <" + name + " > for VERTICES");
	}
	/**********************************************************************************************************/
	void Mesh::ReadVertexData(RefPtr<XmlElement> pElem,const std::string& name)
	{
		if(name == "vertex")
		{
			if(pElem->FirstChildElement().IsNull())
				throw std::logic_error("Missing child nodes for VERTEX node");

			TraverseElemets(pElem->FirstChildElement(),mem_fun2_obj(this,&Mesh::ReadVertex));

			++m_currentVertexIndex;
		}
		else
			throw std::logic_error("Unknown child node <" + name + " > for VERTICES");
	}
	/**********************************************************************************************************/
	void Mesh::ReadVertex(RefPtr<XmlElement> pElem,const std::string& name)
	{
		const VertexElement* element = 0;
		if(name == "position" && (element = m_vertexDeclaration->FindElementBySemantic(VES_POSITION)) != 0)
		{
			ValuesReader<float> reader("x","y","z");
			TraverseAttributes(pElem->FirstAttribute(),mem_fun2_obj(&reader,&ValuesReader<float>::Read));

			float coords[3];
			coords[0] = reader["x"];
			coords[1] = reader["y"];
			coords[2] = reader["z"];
			WriteRawData(coords, 3, element->GetOffset(), m_currentVertexIndex);
			UpdateBBox(Vector3<REAL>(coords[0],coords[1],coords[2]));
		}
		else if(name == "normal" && (element = m_vertexDeclaration->FindElementBySemantic(VES_NORMAL)) != 0)
		{
			ValuesReader<float> reader("x","y","z");
			TraverseAttributes(pElem->FirstAttribute(),mem_fun2_obj(&reader,&ValuesReader<float>::Read));

			float normal[3];
			normal[0] = reader["x"];
			normal[1] = reader["y"];
			normal[2] = reader["z"];
			WriteRawData(normal, 3, element->GetOffset(), m_currentVertexIndex);
		}
		else if(name == "color" && (element = m_vertexDeclaration->FindElementBySemantic(VES_DIFFUSE)) != 0)
		{
			ValuesReader<float> reader("r","g","b","a");
			TraverseAttributes(pElem->FirstAttribute(),mem_fun2_obj(&reader,&ValuesReader<float>::Read));

			Color color(reader["r"],reader["g"],reader["b"],reader["a"]);
			unsigned int argb = color.GetAsARGB();
			WriteRawData(&argb, 1, element->GetOffset(), m_currentVertexIndex);
		}
		else if(name == "texcoord0" && (element = m_vertexDeclaration->FindElementBySemantic(VES_TEXTURE_COORDINATES, 0)) != 0)
		{
			ValuesReader<float> reader("u","v");
			TraverseAttributes(pElem->FirstAttribute(),mem_fun2_obj(&reader,&ValuesReader<float>::Read));

			float uv[2];
			uv[0] = reader["u"];
			uv[1] = reader["v"];
			WriteRawData(uv, 2, element->GetOffset(), m_currentVertexIndex);
		}
		else
			throw std::logic_error("Invalid child node <" + name + "> for VERTEX)");
	}
	/**********************************************************************************************************/
	void Mesh::Load()
	{
		if(IsLoaded())
			return;
		RefPtr<DataStream> file = ArchiveManager::GetSingleton().ReadFile(GetName());
		if(!file.IsNull())
		{
			RefPtr<XmlDocument> doc(new TinyXmlDocument());

			size_t len = file->GetSize();
			RefPtr<char,NoWeakPtrExtension,ArrayDelete> buf(new char[len]);
			file->Read(buf.GetPointer(),len);
			if(doc->Parse(buf.GetPointer()))
			{
				TraverseElemets(doc->FirstChildElement(), mem_fun2_obj(this,&Mesh::ReadMesh));
			}
			else
				throw std::logic_error("Can't parse mesh file : " + GetName() + " : " + doc->ErrorDesc());
			SetLoaded(true);
		}
		else
			throw std::invalid_argument("Can't open mesh file " + GetName() + " Mesh::Load");
	}
	/**********************************************************************************************************/
	void Mesh::UnLoad()
	{
		m_boundingBox.Reset();
		m_vertexDeclaration.Delete();
		m_vertexCount = 0;
		m_vertexData.Delete();
		m_subMeshes.clear();
		SetLoaded(false);
	}
	/**********************************************************************************************************/
	void Mesh::UpdateBBox(Vector3<REAL> vertex)
	{
		Vector3<REAL>& min = m_boundingBox.GetMinimum();
		Vector3<REAL>& max = m_boundingBox.GetMaximum();

		if(min.x > vertex.x)
			min.x = vertex.x;
		if(min.y > vertex.y)
			min.y = vertex.y;
		if(min.z > vertex.z)
			min.z = vertex.z;

		if(max.x < vertex.x)
			max.x = vertex.x;
		if(max.y < vertex.y)
			max.y = vertex.y;
		if(max.z < vertex.z)
			max.z = vertex.z;
	}
	/**********************************************************************************************************/
	const HardWareBufferPtr Mesh::GetVertexData()const
	{
		return m_vertexData;
	}
	/**********************************************************************************************************/
	unsigned int Mesh::GetVertexCount() const
	{
		return m_vertexCount;
	}
	/**********************************************************************************************************/
	const VertexDeclarationPtr Mesh::GetVertexDeclaration() const
	{
		return m_vertexDeclaration;
	}
	/**********************************************************************************************************/
}
